route.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { NextRequest, NextResponse } from "next/server";
  2. import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
  3. import { getServerSideConfig } from "@/app/config/server";
  4. const config = getServerSideConfig();
  5. const mergedAllowedWebDavEndpoints = [
  6. ...internalAllowedWebDavEndpoints,
  7. ...config.allowedWebDevEndpoints,
  8. ].filter((domain) => Boolean(domain.trim()));
  9. async function handle(
  10. req: NextRequest,
  11. { params }: { params: { path: string[] } },
  12. ) {
  13. if (req.method === "OPTIONS") {
  14. return NextResponse.json({ body: "OK" }, { status: 200 });
  15. }
  16. const folder = STORAGE_KEY;
  17. const fileName = `${folder}/backup.json`;
  18. const requestUrl = new URL(req.url);
  19. let endpoint = requestUrl.searchParams.get("endpoint");
  20. // Validate the endpoint to prevent potential SSRF attacks
  21. if (
  22. !mergedAllowedWebDavEndpoints.some(
  23. (allowedEndpoint) => endpoint?.startsWith(allowedEndpoint),
  24. )
  25. ) {
  26. return NextResponse.json(
  27. {
  28. error: true,
  29. msg: "Invalid endpoint",
  30. },
  31. {
  32. status: 400,
  33. },
  34. );
  35. }
  36. if (!endpoint?.endsWith("/")) {
  37. endpoint += "/";
  38. }
  39. const endpointPath = params.path.join("/");
  40. const targetPath = `${endpoint}${endpointPath}`;
  41. // only allow MKCOL, GET, PUT
  42. if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") {
  43. return NextResponse.json(
  44. {
  45. error: true,
  46. msg: "you are not allowed to request " + targetPath,
  47. },
  48. {
  49. status: 403,
  50. },
  51. );
  52. }
  53. // for MKCOL request, only allow request ${folder}
  54. if (req.method === "MKCOL" && !targetPath.endsWith(folder)) {
  55. return NextResponse.json(
  56. {
  57. error: true,
  58. msg: "you are not allowed to request " + targetPath,
  59. },
  60. {
  61. status: 403,
  62. },
  63. );
  64. }
  65. // for GET request, only allow request ending with fileName
  66. if (req.method === "GET" && !targetPath.endsWith(fileName)) {
  67. return NextResponse.json(
  68. {
  69. error: true,
  70. msg: "you are not allowed to request " + targetPath,
  71. },
  72. {
  73. status: 403,
  74. },
  75. );
  76. }
  77. // for PUT request, only allow request ending with fileName
  78. if (req.method === "PUT" && !targetPath.endsWith(fileName)) {
  79. return NextResponse.json(
  80. {
  81. error: true,
  82. msg: "you are not allowed to request " + targetPath,
  83. },
  84. {
  85. status: 403,
  86. },
  87. );
  88. }
  89. const targetUrl = targetPath;
  90. const method = req.method;
  91. const shouldNotHaveBody = ["get", "head"].includes(
  92. method?.toLowerCase() ?? "",
  93. );
  94. const fetchOptions: RequestInit = {
  95. headers: {
  96. authorization: req.headers.get("authorization") ?? "",
  97. },
  98. body: shouldNotHaveBody ? null : req.body,
  99. redirect: "manual",
  100. method,
  101. // @ts-ignore
  102. duplex: "half",
  103. };
  104. let fetchResult;
  105. try {
  106. fetchResult = await fetch(targetUrl, fetchOptions);
  107. } finally {
  108. console.log(
  109. "[Any Proxy]",
  110. targetUrl,
  111. {
  112. method: req.method,
  113. },
  114. {
  115. status: fetchResult?.status,
  116. statusText: fetchResult?.statusText,
  117. },
  118. );
  119. }
  120. return fetchResult;
  121. }
  122. export const PUT = handle;
  123. export const GET = handle;
  124. export const OPTIONS = handle;
  125. export const runtime = "edge";